﻿namespace Microsoft.Web.Helpers.Tests
{
    using System;
    using System.Configuration;
    using System.Data.Services;
    using System.IO;
    using System.Linq;
    using System.Reflection;
    using System.ServiceModel;
    using System.ServiceModel.Web;
    using Microsoft.VisualStudio.TestTools.UnitTesting;
    using Microsoft.Web.Helpers;

    [TestClass]
    public class ODataHelperTests
    {
        private static WebServiceHost webServiceHost;
        private static string mockDataServiceUri = "http://localhost:9999";

        [ClassInitialize]
        public static void InitializeClass(TestContext context)
        {
            // Create the host for the mock data service
            webServiceHost = new WebServiceHost(typeof(MockDataService), new Uri(mockDataServiceUri));
            WebHttpBinding binding = new WebHttpBinding() { MaxReceivedMessageSize = 500000, MaxBufferSize = 500000 };
            webServiceHost.AddServiceEndpoint(typeof(IRequestHandler), binding, "WebServiceHost");
            webServiceHost.Open();
        }

        [ClassCleanup]
        public static void CleanupClass()
        {
            // Close the host
            webServiceHost.Close();
        }

        [TestMethod]
        [ExpectedException(typeof(ArgumentException))]
        public void ShouldThrowExceptionWhenServiceUriIsNotWellFormed()
        {
            var result = OData.Get("mal-formed-uri");
        }

        [TestMethod]
        [ExpectedException(typeof(ArgumentException))]
        public void ShouldThrowExceptionWhenResourceIsNotSpecifiedInServiceUri()
        {
            var result = OData.Get(mockDataServiceUri);
        }

        [TestMethod]
        public void ShouldReturnAllDataFromServiceUsingGet()
        {
            var result = OData.Get(mockDataServiceUri + "/Products");

            Assert.AreEqual(3, result.Count());
            Assert.AreEqual(1, result[0].Id);
            Assert.AreEqual("Product 1", result[0].Name);
            Assert.AreEqual(2, result[1].Id);
            Assert.AreEqual("Product 2", result[1].Name);
            Assert.AreEqual(3, result[2].Id);
            Assert.AreEqual("Product 3", result[2].Name);
        }

        [TestMethod]
        public void ShouldReturnDataFromServiceUsingFilterQueryStringOptionInUrl()
        {
            var result = OData.Get(mockDataServiceUri + "/Products?$filter=Name eq 'Product 1'");

            Assert.AreEqual(1, result.Count());
            Assert.AreEqual(1, result[0].Id);
            Assert.AreEqual("Product 1", result[0].Name);
        }

        [TestMethod]
        public void ShouldReturnDataFromServiceUsingFilterQueryStringOptionAsSeparateParam()
        {
            var result = OData.Get(mockDataServiceUri + "/Products", "$filter=Name eq 'Product 1'");

            Assert.AreEqual(1, result.Count());
            Assert.AreEqual(1, result[0].Id);
            Assert.AreEqual("Product 1", result[0].Name);
        }

        [TestMethod]
        public void ShouldReturnDataFromServiceUsingWhereQueryOptionMethod()
        {
            var result = OData.Open(mockDataServiceUri + "/Products")
                              .Where("Name eq 'Product 1'")
                              .Get();

            Assert.AreEqual(1, result.Count());
            Assert.AreEqual(1, result[0].Id);
            Assert.AreEqual("Product 1", result[0].Name);
        }

        [TestMethod]
        public void ShouldReturnDataFromServiceUsingOrderByQueryOptionMethod()
        {
            var result = OData.Open(mockDataServiceUri + "/Products")
                              .OrderBy("Rating desc")
                              .Get();

            Assert.AreEqual(3, result.Count());
            Assert.AreEqual(3, result[0].Id);
            Assert.AreEqual(1, result[1].Id);
            Assert.AreEqual(2, result[2].Id);
        }

        [TestMethod]
        public void ShouldReturnDataFromServiceUsingSkipTopQueryOptionMethods()
        {
            var result = OData.Open(mockDataServiceUri + "/Products")
                              .Skip(2)
                              .Top(1)
                              .Get();

            Assert.AreEqual(1, result.Count());
            Assert.AreEqual(3, result[0].Id);
            Assert.AreEqual("Product 3", result[0].Name);
        }

        [TestMethod]
        public void ShouldReturnTotalCountFromServiceUsingIncludeTotalCountQueryOptionMethod()
        {
            var result = OData.Open(mockDataServiceUri + "/Products")
                              .IncludeTotalCount()
                              .Skip(2)
                              .Top(1)
                              .Get();

            var totalCount = (result as OData.DynamicEntityCollection).TotalCount;

            Assert.AreEqual(3, totalCount);
            Assert.AreEqual(1, result.Count());
        }

        [TestMethod]
        public void ShouldReturnTotalCountFromServiceUsingInlineCountQueryStringOption()
        {
            var result = OData.Open(mockDataServiceUri + "/Products?inlinecount=allpages")
                              .IncludeTotalCount()
                              .Skip(2)
                              .Top(1)
                              .Get();

            var totalCount = (result as OData.DynamicEntityCollection).TotalCount;

            Assert.AreEqual(3, totalCount);
            Assert.AreEqual(1, result.Count());
        }

        [TestMethod]
        public void ShouldReturnDataFromServiceUsingCumulativeQuerySyntax()
        {
            var result = OData.Open(mockDataServiceUri + "/Products")
                              .OrderBy("Id")
                              .Top(2)
                              .Get();

            Assert.AreEqual(2, result.Count());
            Assert.AreEqual(1, result[0].Id);
            Assert.AreEqual("Product 1", result[0].Name);
            Assert.AreEqual(2, result[1].Id);
            Assert.AreEqual("Product 2", result[1].Name);
        }

        [TestMethod]
        public void ShouldReturnPrimitiveStringDataTypeValueFromService()
        {
            var result = OData.Get(mockDataServiceUri + "/Products(1)");

            Assert.IsInstanceOfType(result[0].Name, typeof(string));
            Assert.AreEqual<string>("Product 1", result[0].Name);
        }

        [TestMethod]
        public void ShouldReturnPrimitiveBoolDataTypeValueFromService()
        {
            var result = OData.Get(mockDataServiceUri + "/Products(1)");

            Assert.IsInstanceOfType(result[0].IsAvailable, typeof(bool));
            Assert.AreEqual<bool>(true, result[0].IsAvailable);
        }

        [TestMethod]
        public void ShouldReturnPrimitiveByteDataTypeValueFromService()
        {
            var result = OData.Get(mockDataServiceUri + "/Products(1)");

            Assert.IsInstanceOfType(result[0].Priority, typeof(byte));
            Assert.AreEqual<byte>(0, result[0].Priority);
        }

        [TestMethod]
        public void ShouldReturnPrimitiveDateTimeDataTypeValueFromService()
        {
            var result = OData.Get(mockDataServiceUri + "/Products(1)");

            Assert.IsInstanceOfType(result[0].Expires, typeof(DateTime));
            Assert.AreEqual<DateTime>(new DateTime(2011, 12, 31), result[0].Expires);
        }

        [TestMethod]
        public void ShouldReturnPrimitiveDecimalDataTypeValueFromService()
        {
            var result = OData.Get(mockDataServiceUri + "/Products(1)");

            Assert.IsInstanceOfType(result[0].Price, typeof(decimal));
            Assert.AreEqual<decimal>(5.1M, result[0].Price);
        }

        [TestMethod]
        public void ShouldReturnPrimitiveDoubleDataTypeValueFromService()
        {
            var result = OData.Get(mockDataServiceUri + "/Products(1)");

            Assert.IsInstanceOfType(result[0].Rating, typeof(double));
            Assert.AreEqual<double>(2.5, result[0].Rating);
        }

        [TestMethod]
        public void ShouldReturnPrimitiveSingleDataTypeValueFromService()
        {
            var result = OData.Get(mockDataServiceUri + "/Products(1)");

            Assert.IsInstanceOfType(result[0].Cost, typeof(float));
            Assert.AreEqual<float>(1.5f, result[0].Cost);
        }

        [TestMethod]
        public void ShouldReturnPrimitiveGuidDataTypeValueFromService()
        {
            var result = OData.Get(mockDataServiceUri + "/Products(1)");

            Assert.IsInstanceOfType(result[0].GlobalId, typeof(Guid));
            Assert.AreEqual<Guid>(new Guid("12345678-aaaa-bbbb-cccc-ddddeeeeffff"), result[0].GlobalId);
        }

        [TestMethod]
        public void ShouldReturnPrimitiveInt16DataTypeValueFromService()
        {
            var result = OData.Get(mockDataServiceUri + "/Products(1)");

            Assert.IsInstanceOfType(result[0].Category, typeof(short));
            Assert.AreEqual<short>(1, result[0].Category);
        }

        [TestMethod]
        public void ShouldReturnPrimitiveInt32DataTypeValueFromService()
        {
            var result = OData.Get(mockDataServiceUri + "/Products(1)");

            Assert.IsInstanceOfType(result[0].Id, typeof(int));
            Assert.AreEqual<int>(1, result[0].Id);
        }

        [TestMethod]
        public void ShouldReturnPrimitiveInt64DataTypeValueFromService()
        {
            var result = OData.Get(mockDataServiceUri + "/Products(1)");

            Assert.IsInstanceOfType(result[0].AnualProfits, typeof(long));
            Assert.AreEqual<long>(201200000L, result[0].AnualProfits);
        }

        [TestMethod]
        public void ShouldReturnPrimitiveSByteDataTypeValueFromService()
        {
            var result = OData.Get(mockDataServiceUri + "/Products(1)");

            Assert.IsInstanceOfType(result[0].Status, typeof(sbyte));
            Assert.AreEqual<sbyte>(5, result[0].Status);
        }

        [TestMethod]
        public void ShouldReturnNullPropertyValueFromService()
        {
            var result = OData.Get(mockDataServiceUri + "/Products(1)");

            Assert.IsNull(result[0].Stock);
        }

        [TestMethod]
        public void ShouldReturnComplexPropertyAsNestedDynamicPropertyFromService()
        {
            var result = OData.Get(mockDataServiceUri + "/Products(1)");

            Assert.IsNotNull(result[0].Supplier);
            Assert.IsNotNull(result[0].Supplier.Name);
            Assert.IsNotNull(result[0].Supplier.Address);
        }

        [TestMethod]
        [DeploymentItem("Resources/Koala.jpg")]
        public void ShouldPostMediaLinkToService()
        {
            var image = "Koala.jpg";
            var uploadedImage = "uploaded-image.jpg";

            Assert.IsTrue(!File.Exists(uploadedImage));
            Assert.AreEqual(3, MockServiceEntities.ImageList.Count());

            using (var stream = new FileStream(image, FileMode.Open))
            {
                dynamic mediaLink = OData.CreateMediaLink(stream, "image/jpg");
                mediaLink.Name = "New Photo";
                OData.Insert("http://localhost:9999/Images", mediaLink);
            }

            Assert.IsTrue(File.Exists(uploadedImage));
            var actualImageContent = File.ReadAllBytes(image);
            var expectedImageContent = File.ReadAllBytes(uploadedImage);
            Assert.AreEqual(expectedImageContent.Length, actualImageContent.Length);

            var insertedEntity = MockServiceEntities.ImageList.Last();
            Assert.AreEqual(4, MockServiceEntities.ImageList.Count());
            Assert.AreEqual("New Photo", insertedEntity.Name);

            MockServiceEntities.ImageList.Remove(insertedEntity);
        }

        [TestMethod]
        public void ShouldPostEntityToService()
        {
            var entity = OData.CreateEntity();
            entity.Id = 4;
            entity.Name = "New Product";

            Assert.AreEqual(3, MockServiceEntities.ProductList.Count());

            OData.Insert("http://localhost:9999/Products", entity);

            var insertedEntity = MockServiceEntities.ProductList.Last();

            Assert.AreEqual(4, MockServiceEntities.ProductList.Count());
            Assert.AreEqual(4, insertedEntity.Id);
            Assert.AreEqual("New Product", insertedEntity.Name);

            MockServiceEntities.ProductList.Remove(insertedEntity);
        }

        [TestMethod]
        public void ShouldPostEntityToServiceWithComplexTypeProperties()
        {
            var entity = OData.CreateEntity();
            entity.Id = 4;
            entity.Name = "New Product";
            entity.Supplier = OData.CreateEntity();
            entity.Supplier.Name = "Supplier 1";
            entity.Supplier.Address = "Address 1";

            Assert.AreEqual(3, MockServiceEntities.ProductList.Count());

            OData.Insert("http://localhost:9999/Products", entity);

            var insertedEntity = MockServiceEntities.ProductList.Last();

            Assert.AreEqual(4, MockServiceEntities.ProductList.Count());
            Assert.IsNotNull(insertedEntity.Supplier);
            Assert.AreEqual("Supplier 1", insertedEntity.Supplier.Name);
            Assert.AreEqual("Address 1", insertedEntity.Supplier.Address);

            MockServiceEntities.ProductList.Remove(insertedEntity);
        }

        [TestMethod]
        public void ShouldUpdateEntity()
        {
            var entity = OData.Get("http://localhost:9999/Products(2)").FirstOrDefault();
            entity.Name = "Product 2 updated";

            OData.Update("http://localhost:9999/Products(2)", entity);

            var updatedEntity = MockServiceEntities.ProductList.First(p => p.Id == 2);

            Assert.AreEqual("Product 2 updated", updatedEntity.Name);
            updatedEntity.Name = "Product 2";
        }

        [TestMethod]
        public void ShouldDeleteEntity()
        {
            var entityToDelete = MockServiceEntities.ProductList.FirstOrDefault(p => p.Id == 3);
            Assert.IsNotNull(entityToDelete);

            OData.Delete("http://localhost:9999/Products(3)");

            Assert.IsNull(MockServiceEntities.ProductList.FirstOrDefault(p => p.Id == 3));
            MockServiceEntities.ProductList.Add(entityToDelete);
        }
    }
}
